package org.raymond.iworks.gateway.filter;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.net.URLDecoder;
import cn.hutool.core.net.url.UrlQuery;
import cn.hutool.crypto.symmetric.AES;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.raymond.iworks.apis.common.ErrorCode;
import org.raymond.iworks.apis.exception.BaseException;
import org.raymond.iworks.commons.constants.CommonConstants;
import org.raymond.iworks.commons.utils.ErrorUtil;
import org.raymond.iworks.core.utils.FilterUtil;
import org.raymond.iworks.gateway.filter.support.FilterOrder;
import org.raymond.iworks.gateway.filter.support.GatewayContext;
import org.raymond.iworks.gateway.utils.SignUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.UnsatisfiedRequestParameterException;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.List;
import java.util.Map;

@Slf4j
@Component
public class ModifiedParametersFilter implements GatewayFilter, Ordered {
    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private SignUtil signUtil;

    private MultiValueMap parseRequestParams(String requestString){
        MultiValueMap paramsMap = new LinkedMultiValueMap();
        LinkedMultiValueMap<String, String> map = null;
        try {
            map = objectMapper.readValue(requestString, LinkedMultiValueMap.class);
        } catch (JsonProcessingException e) {
            log.error("{}", ErrorUtil.getErrorLog(e));
        }
        if (map != null && map.size() > 0) {
            for (Map.Entry<String, List<String>> entry : map.entrySet()) {
                final String name = entry.getKey();
                final List<String> value = entry.getValue();
                if (name == null || value == null){
                    continue;
                }
                paramsMap.put(name, value);
            }
        }
        return paramsMap;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        URI requestUri = request.getURI();

        GatewayContext gatewayContext = exchange.getAttribute(GatewayContext.CACHE_GATEWAY_CONTEXT);
        if (gatewayContext == null) {
            throw new BaseException(ErrorCode.ERROR, "Gateway context should not be null");
        }
        // 服务端请求时间,即请求到达服务端的时间
        Date now = new Date();
        String nowStr = DateUtil.format(now, DatePattern.NORM_DATETIME_MS_FORMAT);
        gatewayContext.setRequestToServer(now);
        ServerHttpRequest.Builder requestBuilder = request.mutate().headers(httpHeaders ->
            httpHeaders.add(CommonConstants.REQUEST_TO_GATEWAY, nowStr)
        );
        ServerHttpRequest newRequest = requestBuilder.build();
        exchange = exchange.mutate().request(newRequest).build();

        String method = request.getMethod().name();
        if(!HttpMethod.GET.matches(method)){
            return chain.filter(exchange);
        }

        AES aes = gatewayContext.getAes();
        String decryptedSign = null; //gatewayContext.getSign();
        String paramString = FilterUtil.getRequestParams(request);

        if (StringUtils.isBlank(paramString)) {
            return chain.filter(exchange);
        }
        int paramsIdx = paramString.indexOf("=");
        if (paramsIdx == -1) {
            throw new BaseException(ErrorCode.BAD_REQUEST, String.format("Params template: %s", CommonConstants.PARAMS_TEMPLATE));
        }
        String urlParams = paramString.substring(paramsIdx);
        log.debug("requestString:{}, params:{}", paramString, urlParams);
        if (StringUtils.isBlank(urlParams)) {
            return chain.filter(exchange);
        }
        if (aes != null) {
            paramString = aes.decryptStr(urlParams);
        } else {
            paramString = Base64.decodeStr(urlParams);
        }
        paramString = URLDecoder.decode(paramString, StandardCharsets.UTF_8);
        if (log.isDebugEnabled()) {
            log.debug("requestString:{}", paramString);
        }
        MultiValueMap paramsMap = parseRequestParams(paramString);
        UrlQuery urlQuery = UrlQuery.of(paramsMap);
        paramString = urlQuery.build(StandardCharsets.UTF_8);
        if (StringUtils.isNotBlank(decryptedSign)) {
            if (!signUtil.checkSign(paramsMap, decryptedSign)) {
                throw new BaseException(ErrorCode.SIGN_CHECK_FAIL);
            }
        }
        try {
            URI newUri = UriComponentsBuilder.fromUri(requestUri).replaceQuery(paramString).build(true).toUri();
            newRequest = newRequest.mutate().uri(newUri).build();
            exchange = exchange.mutate().request(newRequest).build();
            return chain.filter(exchange);
        } catch (RuntimeException ex) {
            throw new IllegalStateException("Invalid URI query: \"" + paramString + "\"");
        }
    }

    @Override
    public int getOrder() {
        return FilterOrder.ModifiedParametersFilter;
    }
}
